package _runnable_classes;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.TimeZone;

import primary_classes.OrderBookList;
import primary_classes.SystemVars;
import primary_classes.SystemVars.STATUS;
import secondary_classes.Order;
import secondary_classes.ReadOrders;
import secondary_classes.StopWatch;

/**
 * @author Rohit
 * 
 */
public class TradingEngine {

	// CONSTANTS
	public  static enum   PHASE {closed, preOpen, opening, normalTrading, endOfTrading, preClosing, closing, adjustment, purgeOrders, systemMaintenance, systemUnavailable};
	public  static        ArrayList<PHASE>    phases          = new ArrayList<PHASE>(Arrays.asList(PHASE.values()));
	private static        String              SYSTEM_VARS_IN  = "SystemVars.txt";
	private static final  String              SYSTEM_VARS_OUT = "status.txt";
	private static final  DateFormat          timeFormat      = new SimpleDateFormat("HH:mm:ss.SSS");
	private static HashMap<PHASE, Date>       phaseTimes      = new HashMap<PHASE, Date>();
	
	// ATTRIBUTES
	public  static ReadOrders    ordersData;
	private static SystemVars    systemVars;
	private static OrderBookList orderBooks;
	private static StopWatch     s = new StopWatch();
	private static PHASE         currentPhase;
	private static int           currentPhaseIndex;

	private static boolean console = false;
	/**
	 * Do not call this method.  Used in the console version of the application
	 */
	public static void main(String[] args) {
		console = true;
		if (args.length == 1) {
			SYSTEM_VARS_IN = args[0];
		}
		
		new TradingEngine("", "", "", 0);
		
	}
	
	/**
	 * Creates a new TradingEngine
	 * @param inputFile the name of the input file (including file extension)
	 * @param inputDir location of the input file
	 * @param outputFileDir location of the output directory
	 * @param marketDepthSize the market depth size
	 */
	public TradingEngine(String inputFile, String inputDir, String outputFileDir, int marketDepthSize) {
		String error = "";
		
	    s.start();
	    //code you want to time goes here

		try {
			initialize();
			
			System.out.print(elapsedTimeString() + "  READING SYSTEM VARIABLES" + "\r");
			if (console) {
				systemVars = new SystemVars(SYSTEM_VARS_IN, SYSTEM_VARS_OUT);
			} else {
				systemVars = new SystemVars(inputFile, inputDir, SYSTEM_VARS_OUT, outputFileDir, marketDepthSize);
			}
			System.out.println(elapsedTimeString() + "  READING SYSTEM VARIABLES - Done");
			
			readInput();
			currentPhase = PHASE.closed;

			populateOrderBooks();
			
			orderBooks.writeMarketDepthFile(SystemVars.getMarketDepthSize(), SystemVars.getMarketDepthFile());
			orderBooks.writeTradesFile(SystemVars.getTradesFile());
			
			s.stop();
			
			writePerformanceStats(s, SystemVars.getLogFile());
			
			SystemVars.generateStatusFile(STATUS.OK, "");
		} catch (Exception e) {
			s.stop();
			error = e.getMessage();
			System.err.println();
			e.printStackTrace(System.err);
			SystemVars.generateStatusFile(STATUS.ERR, error);
			
		}
		
	}

	private static void initialize() throws Exception {
		
		try {
			phaseTimes.put(PHASE.closed,            timeFormat.parse("02:25:00.000"));
			phaseTimes.put(PHASE.preOpen,           timeFormat.parse("07:00:00.000"));
			phaseTimes.put(PHASE.opening,           timeFormat.parse("10:00:00.000"));
			phaseTimes.put(PHASE.normalTrading,     timeFormat.parse("10:00:00.000"));
			phaseTimes.put(PHASE.endOfTrading,      timeFormat.parse("16:00:00.000"));
			phaseTimes.put(PHASE.preClosing,        timeFormat.parse("16:10:00.000"));
			phaseTimes.put(PHASE.closing,           timeFormat.parse("16:12:00.000"));
			phaseTimes.put(PHASE.adjustment,        timeFormat.parse("18:50:00.000"));
			phaseTimes.put(PHASE.purgeOrders,       timeFormat.parse("18:59:00.000"));
			phaseTimes.put(PHASE.systemMaintenance, timeFormat.parse("19:00:00.000"));
			phaseTimes.put(PHASE.systemUnavailable, timeFormat.parse("21:30:00.000"));
			
			timeFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
			currentPhase = PHASE.systemUnavailable;
			currentPhaseIndex = 0;
		} catch (Exception e) {
			throw new Exception("Incorrectly coded phase times");
		}
		
	}

	private static void readInput() throws Exception {
		// read in the input file supplied by the SystemVars
		if (systemVars == null) {
			throw new Exception("System variables not entered correctly");
		}
		
		String inputFilePath = SystemVars.getInputFile().getAbsolutePath();
		
		System.out.print(TradingEngine.elapsedTimeString() + "  READING INPUT" + "\r");
		ordersData = new ReadOrders(inputFilePath, false);
		
		orderBooks = new OrderBookList();
	}
	
	public static int orderNum = 0;
	private static void populateOrderBooks() throws Exception {
		try {
			String recordType;
			Order currentRecord;
			int numOrders = ordersData.size();
			for (int j = 0; j < numOrders; j++) {
				orderNum = j + 1;
				System.out.print(elapsedTimeString() + "  POPULATING ORDERBOOKS - Processing order " + orderNum + " of " + numOrders + "\r");
				currentRecord = ordersData.getRecord(j);
				recordType = currentRecord.getRecordType();
				currentPhase = calculatePhase(currentRecord);
				boolean changed = false;
				
				
				if (recordType.equals("ENTER") && (phases.indexOf(currentPhase) >= phases.indexOf(PHASE.preOpen) && phases.indexOf(currentPhase) <= phases.indexOf(PHASE.closing))) {
					orderBooks.enter(currentRecord);
					changed = true;
				} else if (recordType.equals("DELETE") && !(currentPhase.equals(PHASE.closed) || currentPhase.equals(PHASE.systemUnavailable))) {
					orderBooks.delete(currentRecord);
					changed = true;
				} else if (recordType.equals("AMEND") && !(currentPhase.equals(PHASE.closed) || currentPhase.equals(PHASE.systemUnavailable))) {
					orderBooks.amend(currentRecord);
					changed = true;
				}
				
				if (changed) {
					if(currentPhase.equals(PHASE.opening)){
						orderBooks.opening(currentRecord);
					} else if (currentPhase.equals(PHASE.closing)) {
						orderBooks.closing();
					}
				}
				
			}
			System.out.println(elapsedTimeString() + "  POPULATING ORDERBOOKS - Done (Read " + orderNum + " orders)               ");
		} catch (Exception e) {
			Exception x = new Exception("Order " + orderNum + ": " + e.getMessage());
			x.setStackTrace(e.getStackTrace());
			throw x;
		}
	}
	
	/**
	 * Calculates the phase in which an order was entered, depending on it's time.
	 * @param o The order to check the phase of
	 * @return The {@link PHASE} of the order.  If the phase could not be calculated, this function returns the phase {@link PHASE#systemUnavailable}.
	 */
	private static boolean opened = false;
	private static boolean closed = false;
	private static PHASE calculatePhase(Order o) {
		PHASE result = null, p = null;
		
		for (int i = phaseTimes.size() - 1; i > currentPhaseIndex && result == null; i--) {
			p = PHASE.values()[i];
			if (o.getTime().compareTo(phaseTimes.get(p)) >= 0) {
				currentPhaseIndex = Arrays.asList(PHASE.values()).indexOf(currentPhase);
				result = p;
			}
		}
		
		if (result == null) {
			result = currentPhase;
		} else if (result.equals(PHASE.normalTrading) && !opened) {
			result = PHASE.opening;
			opened = true;
			closed = false;
		} else if (result.equals(PHASE.closing) && closed) {
			result = PHASE.adjustment;
			closed = true;
			opened = false;
		} else if (result.equals(PHASE.preOpen)) {
			opened = false;
			closed = false;
		}
		
		return result;
	}

	private static void writePerformanceStats(StopWatch s, File file) throws Exception {
		System.out.print(elapsedTimeString() + "  PERFORMANCE LOG" + "\r");
		// create output file
		BufferedWriter b;
		try {
			b = new BufferedWriter(new FileWriter(file));
		} catch (Exception e) {
			throw new Exception("Could not write log file.");
		}

		String result = elapsedTimeString();
		
		b.write(result);
		b.close();
		System.out.println(elapsedTimeString() + "  PERFORMANCE LOG - Done");
	}
	
	public static String elapsedTimeString() {
		Date d = new Date(s.getElapsedTime());
		return "Elapsed Time = " + timeFormat.format(d);
	}

	public static ReadOrders getOrdersData() {
		return ordersData;
	}
	
	public static OrderBookList getOrderBooks() {
		return orderBooks;
	}
	
	public static PHASE getCurrentPhase() {
		return currentPhase;
	}
	
}
